#include <optional>
#include <SFML/Graphics/Texture.hpp>
#include <nice2d/application.h>
#include <nice2d/components/imagemanager.h>
#include <nice2d/imageset.h>

class ImageSetData
{
public:
    std::vector<std::pair<std::string, sf::IntRect>> images;
    std::optional<sf::Vector2u> size;

    PTree serialize(const std::string &file, const sf::IntRect &area)
    {
        PTree node;
        node.add("file", file);
        node.add_child("area", serializeObject(area));
        return node;
    }

    void deserialize(const PTree &node, std::string &file, sf::IntRect &area)
    {
        file = node.get_value("file");
        auto opt = node.get_child_optional("area");
        if (opt)
            area = deserializeIntRect(opt.value());
    }
};

IMPLEMENT_OBJECT(ImageSet, Serializer)

ImageSet::ImageSet():
    data(new ImageSetData())
{
}

ImageSet::~ImageSet()
{
}

bool ImageSet::load(const std::string &file)
{
    PTree ptree;
    boost::property_tree::xml_parser::read_xml<PTree>(file, ptree);
    if (ptree.empty())
        return false;

    ptree = ptree.get_child(getObjectName());
    deserialize(ptree);
    return !data->images.empty();
}

sf::Vector2u ImageSet::getImageSize() const
{
    return data->size.has_value() ? data->size.value() : sf::Vector2u();
}

bool ImageSet::addImage(const std::string &file, const sf::IntRect &area)
{
    sf::Texture texture;
    if (!texture.loadFromFile(file, area))
        return false;

    if (data->images.empty())
        data->size = texture.getSize();
    else if (texture.getSize() != data->size)
        return false;

    data->images.push_back({file, area});
    return true;
}

void ImageSet::visitImage(std::function<void(const std::string &, const sf::IntRect &)> visitor)
{
    std::for_each(data->images.begin(), data->images.end(), [&](const auto & node) {
        visitor(node.first, node.second);
    });
}

PTree ImageSet::serialize()
{
    PTree node;

    if (data->size.has_value())
        node.add_child("size", serializeObject(data->size.value()));

    PTree items;
    visitImage([&](const std::string & file, const sf::IntRect & area) {
        PTree item;
        item.add("file", file);
        item.add_child("area", serializeObject(area));
        items.add_child("item", item);
    });

    node.add_child("items", items);
    return node;
}

void ImageSet::deserialize(const PTree &node)
{
    auto items = node.get_child("items");
    auto itr = items.begin();
    while (itr != items.end()) {
        auto itemNode = *itr;
        std::string file = itemNode.second.get<std::string>("file");
        sf::IntRect area = deserializeIntRect(itemNode.second.get_child("area"));
        data->images.push_back({file, area});
        itr ++;
    }

    auto size = node.get_child_optional("size");
    if (size.has_value())
        data->size = deserializeVector2u(size.value());
}
